// ============================================================================
//
// Copyright (C) 2006-2015 Talend Inc. - www.talend.com
//
// This source code is available under agreement available at
// %InstallDIR%\features\org.talend.rcp.branding.%PRODUCTNAME%\%PRODUCTNAME%license.txt
//
// You should have received a copy of the agreement
// along with this program; if not, write to Talend SA
// 9 rue Pages 92150 Suresnes, France
//
// ============================================================================
package org.talend.dataprofiler.core.ui.dialog;

import java.util.HashMap;
import java.util.Map;

import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.commands.operations.IUndoableOperation;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.dialogs.TrayDialog;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.ide.undo.CreateMarkersOperation;
import org.eclipse.ui.ide.undo.UpdateMarkersOperation;
import org.eclipse.ui.ide.undo.WorkspaceUndoUtil;
import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;
import org.eclipse.ui.views.markers.internal.MarkerMessages;
import org.eclipse.ui.views.markers.internal.Util;

/**
 * 
 * DOC mzhao class global comment. Detailled comment
 */
@SuppressWarnings("restriction")
public class TdDialogMarkerProperties extends TrayDialog {

    private static final String DIALOG_SETTINGS_SECTION = "DialogMarkerPropertiesDialogSettings"; //$NON-NLS-1$

    /**
     * The marker being shown, or <code>null</code> for a new marker.
     */
    private IMarker marker = null;

    /**
     * The resource on which to create a new marker.
     */
    private IResource resource = null;

    /**
     * The type of marker to be created.
     */
    private String type = IMarker.MARKER;

    /**
     * The initial attributes to use when creating a new marker.
     */
    @SuppressWarnings("unchecked")
    private Map initialAttributes = null;

    /**
     * The text control for the Description field.
     */
    private Text descriptionText;

    /**
     * The control for the Creation Time field.
     */
    private Label creationTime;

    /**
     * The text control for the Resource field.
     */
    private Text resourceText;

    /**
     * The text control for the Folder field.
     */
    private Text folderText;

    /**
     * The text control for the Location field.
     */
    private Text locationText;

    /**
     * Dirty flag. True if any changes have been made.
     */
    private boolean dirty;

    private String title;

    /**
     * The name used to describe the specific kind of marker. Used when creating an undo command for the dialog, so that
     * a specific name such as "Undo Create Task" or "Undo Modify Bookmark" can be used.
     */
    private String markerName;

    /**
     * Creates the dialog. By default this dialog creates a new marker. To set the resource and initial attributes for
     * the new marker, use <code>setResource</code> and <code>setInitialAttributes</code>. To show or modify an existing
     * marker, use <code>setMarker</code>.
     * 
     * @param parentShell the parent shell
     */
    public TdDialogMarkerProperties(Shell parentShell) {
        super(parentShell);
    }

    /**
     * Creates the dialog. By default this dialog creates a new marker. To set the resource and initial attributes for
     * the new marker, use <code>setResource</code> and <code>setInitialAttributes</code>. To show or modify an existing
     * marker, use <code>setMarker</code>.
     * 
     * @param parentShell the parent shell
     * @param title the title of the dialog
     */
    public TdDialogMarkerProperties(Shell parentShell, String title) {
        super(parentShell);
        this.title = title;
    }

    /**
     * Creates the dialog. By default this dialog creates a new marker. To set the resource and initial attributes for
     * the new marker, use <code>setResource</code> and <code>setInitialAttributes</code>. To show or modify an existing
     * marker, use <code>setMarker</code>.
     * 
     * @param parentShell the parent shell
     * @param title the title of the dialog
     * @param markerName the name used to describe the specific kind of marker shown
     * 
     * @since 3.3
     */
    public TdDialogMarkerProperties(Shell parentShell, String title, String markerName) {
        super(parentShell);
        this.title = title;
        this.markerName = markerName;
    }

    /**
     * Sets the marker to show or modify.
     * <p>
     * IMPORTANT: Although this class is internal, there are public subclasses that expose this method as API. Changes
     * in this implementation should be treated as API changes.
     * 
     * @param marker the marker, or <code>null</code> to create a new marker
     * 
     * @since 3.3
     */
    public void setMarker(IMarker marker) {
        this.marker = marker;
        if (marker != null) {
            try {
                type = marker.getType();
            } catch (CoreException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * Returns the marker being created or modified. For a new marker, this returns <code>null</code> until the dialog
     * returns, but is non-null after.
     * <p>
     * IMPORTANT: Although this method is protected and the class is internal, there are public subclasses that expose
     * this method as API. Changes in this implementation should be treated as API changes.
     * 
     * @return the marker
     * 
     * @since 3.3
     */
    protected IMarker getMarker() {
        return marker;
    }

    /**
     * Sets the resource to use when creating a new task. If not set, the new task is created on the workspace root.
     * <p>
     * IMPORTANT: Although this class is internal, there are public subclasses that expose this method as API. Changes
     * in this implementation should be treated as API changes.
     * 
     * @param resource the resource
     */
    public void setResource(IResource resource) {
        this.resource = resource;
    }

    /**
     * Returns the resource to use when creating a new task, or <code>null</code> if none has been set. If not set, the
     * new task is created on the workspace root.
     * <p>
     * IMPORTANT: Although this method is protected and the class is internal, there are public subclasses that expose
     * this method as API. Changes in this implementation should be treated as API changes.
     * 
     * @return the resource
     * 
     * @since 3.3
     */
    protected IResource getResource() {
        return resource;
    }

    /**
     * Sets initial attributes to use when creating a new task. If not set, the new task is created with default
     * attributes.
     * <p>
     * IMPORTANT: Although this method is protected and the class is internal, there are public subclasses that expose
     * this method as API. Changes in this implementation should be treated as API changes.
     * 
     * @param initialAttributes the initial attributes
     * 
     * @since 3.3
     */
    @SuppressWarnings("unchecked")
    public void setInitialAttributes(Map initialAttributes) {
        this.initialAttributes = initialAttributes;
    }

    /**
     * Returns the initial attributes to use when creating a new task, or <code>null</code> if not set. If not set, the
     * new task is created with default attributes.
     * <p>
     * IMPORTANT: Although this method is protected and the class is internal, there are public subclasses that expose
     * this method as API. Changes in this implementation should be treated as API changes.
     * 
     * @return the initial attributes
     * 
     * @since 3.3
     */

    @SuppressWarnings("unchecked")
    protected Map getInitialAttributes() {
        if (initialAttributes == null) {
            initialAttributes = new HashMap();
        }
        return initialAttributes;
    }

    /**
     * Method declared on Window.
     */
    protected void configureShell(Shell newShell) {
        super.configureShell(newShell);
        if (title == null) {
            newShell.setText(MarkerMessages.propertiesDialog_title);//$NON-NLS-1$
        } else {
            newShell.setText(title);
        }
    }

    /**
     * Method declared on Dialog.
     */
    protected Control createDialogArea(Composite parent) {
        // initialize resources/properties
        if (marker != null) {
            resource = marker.getResource();
            try {
                initialAttributes = marker.getAttributes();
            } catch (CoreException e) {
                e.printStackTrace();
            }
        } else if (resource == null) {
            resource = ResourcesPlugin.getWorkspace().getRoot();
        }

        Composite comp = (Composite) super.createDialogArea(parent);
        Composite composite = new Composite(comp, SWT.NULL);
        GridLayout layout = new GridLayout(2, false);
        layout.marginWidth = 0;
        layout.marginHeight = 0;
        composite.setLayout(layout);
        GridData gridData = new GridData(GridData.FILL_HORIZONTAL);
        composite.setLayoutData(gridData);

        initializeDialogUnits(composite);
        createDescriptionArea(composite);
        if (marker != null) {
            createSeperator(composite);
            createCreationTimeArea(composite);
        }
        createAttributesArea(composite);
        if (resource != null) {
            createSeperator(composite);
            createResourceArea(composite);
        }
        updateDialogFromMarker();
        updateEnablement();

        Dialog.applyDialogFont(composite);

        return composite;
    }

    /**
     * Creates a seperator.
     */
    protected void createSeperator(Composite parent) {
        Label seperator = new Label(parent, SWT.NULL);
        GridData gridData = new GridData(GridData.FILL_HORIZONTAL);
        gridData.horizontalSpan = 2;
        seperator.setLayoutData(gridData);
    }

    /**
     * Method createCreationTimeArea.
     * 
     * @param parent
     */
    private void createCreationTimeArea(Composite parent) {
        Label label = new Label(parent, SWT.NONE);
        label.setText(MarkerMessages.propertiesDialog_creationTime_text);//$NON-NLS-1$ 

        creationTime = new Label(parent, SWT.NONE);
    }

    /**
     * Creates the OK and Cancel buttons.
     */
    protected void createButtonsForButtonBar(Composite parent) {
        createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true);
        createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, false);
    }

    /**
     * Creates the area for the Description field.
     */
    private void createDescriptionArea(Composite parent) {
        Label label = new Label(parent, SWT.NONE);
        label.setText(MarkerMessages.propertiesDialog_description_text);//$NON-NLS-1$
        descriptionText = new Text(parent, (SWT.SINGLE | SWT.BORDER));
        GridData gridData = new GridData(GridData.FILL_HORIZONTAL);
        gridData.widthHint = convertHorizontalDLUsToPixels(400);
        descriptionText.setLayoutData(gridData);

        descriptionText.addModifyListener(new ModifyListener() {

            public void modifyText(ModifyEvent e) {
                markDirty();
            }
        });
    }

    /**
     * This method is intended to be overridden by subclasses. The attributes area is created between the creation time
     * area and the resource area.
     * 
     * @param parent the parent composite
     */
    protected void createAttributesArea(Composite parent) {
    }

    /**
     * Creates the area for the Resource field.
     */
    private void createResourceArea(Composite parent) {
        Label resourceLabel = new Label(parent, SWT.NONE);
        resourceLabel.setText(MarkerMessages.propertiesDialog_resource_text);//$NON-NLS-1$
        resourceText = new Text(parent, SWT.SINGLE | SWT.WRAP | SWT.READ_ONLY | SWT.BORDER);
        GridData gridData = new GridData(GridData.FILL_HORIZONTAL);
        resourceText.setLayoutData(gridData);

        Label folderLabel = new Label(parent, SWT.NONE);
        folderLabel.setText(MarkerMessages.propertiesDialog_folder_text);
        folderText = new Text(parent, SWT.SINGLE | SWT.WRAP | SWT.READ_ONLY | SWT.BORDER);
        gridData = new GridData(GridData.FILL_HORIZONTAL);
        folderText.setLayoutData(gridData);

        Label locationLabel = new Label(parent, SWT.NONE);
        locationLabel.setText(MarkerMessages.propertiesDialog_location_text);
        locationText = new Text(parent, SWT.SINGLE | SWT.WRAP | SWT.READ_ONLY | SWT.BORDER);
        gridData = new GridData(GridData.FILL_HORIZONTAL);
        locationText.setLayoutData(gridData);
    }

    /**
     * Updates the dialog from the marker state.
     */
    protected void updateDialogFromMarker() {
        if (marker == null) {
            updateDialogForNewMarker();
            return;
        }
        descriptionText.setText(Util.getProperty(IMarker.MESSAGE, marker));
        if (creationTime != null) {
            creationTime.setText(Util.getCreationTime(marker));
        }
        if (resourceText != null) {
            resourceText.setText(Util.getResourceName(marker));
        }
        if (folderText != null) {
            folderText.setText(Util.getContainerName(marker));
        }
        if (locationText != null) {
            String line = Util.getProperty(IMarker.LINE_NUMBER, marker);
            if (line.equals("")) { //$NON-NLS-1$
                locationText.setText(""); //$NON-NLS-1$
            } else {
                locationText.setText(NLS.bind(MarkerMessages.label_lineNumber, line));
            }
        }

        descriptionText.selectAll();
    }

    /**
     * Updates the dialog from the predefined attributes.
     */
    protected void updateDialogForNewMarker() {
        if (resource != null && resourceText != null && folderText != null) {
            resourceText.setText(resource.getName());

            IPath path = resource.getFullPath();
            int n = path.segmentCount() - 1; // n is the number of segments in container, not path
            if (n > 0) {
                int len = 0;
                for (int i = 0; i < n; ++i) {
                    len += path.segment(i).length();
                }
                // account for /'s
                if (n > 1) {
                    len += n - 1;
                }
                StringBuffer sb = new StringBuffer(len);
                for (int i = 0; i < n; ++i) {
                    if (i != 0) {
                        sb.append('/');
                    }
                    sb.append(path.segment(i));
                }
                folderText.setText(sb.toString());
            }
        }

        if (initialAttributes != null) {
            Object description = initialAttributes.get(IMarker.MESSAGE);
            if (description != null && description instanceof String) {
                descriptionText.setText((String) description);
            }
            descriptionText.selectAll();

            Object line = initialAttributes.get(IMarker.LINE_NUMBER);
            if (line != null && locationText != null) {
                locationText.setText(line.toString());
            }
        }
    }

    /**
     * Method declared on Dialog.
     */
    protected void okPressed() {
        if (marker == null || Util.isEditable(marker)) {
            saveChanges();
        }
        super.okPressed();
    }

    /**
     * Sets the dialog's dirty flag to <code>true</code>.
     */
    protected void markDirty() {
        dirty = true;
    }

    /**
     * @return <ul>
     * <li><code>true</code> if the dirty flag has been set to true.</li> <li><code>false</code> otherwise.</li>
     * </ul>
     */
    protected boolean isDirty() {
        return dirty;
    }

    /**
     * Saves the changes made in the dialog if needed. Creates a new marker if needed. Updates the existing marker only
     * if there have been changes.
     */
    @SuppressWarnings("unchecked")
    private void saveChanges() {
        Map attrs = getMarkerAttributes();
        IUndoableOperation op = null;
        if (marker == null) {
            if (resource == null)
                return;
            op = new CreateMarkersOperation(type, attrs, resource, getCreateOperationTitle());
        } else {
            if (isDirty()) {
                op = new UpdateMarkersOperation(marker, attrs, getModifyOperationTitle(), true);
            }
        }
        if (op != null) {
            try {
                PlatformUI.getWorkbench().getOperationSupport().getOperationHistory().execute(op, null,
                        WorkspaceUndoUtil.getUIInfoAdapter(getShell()));
            } catch (ExecutionException e) {
                if (e.getCause() instanceof CoreException) {
                    ErrorDialog.openError(getShell(), MarkerMessages.Error, null, ((CoreException) e.getCause()).getStatus());
                } else {
                    IDEWorkbenchPlugin.log(e.getMessage(), e);
                }
            }
        }
    }

    /**
     * Returns the marker attributes to save back to the marker, based on the current dialog fields.
     */
    @SuppressWarnings("unchecked")
    protected Map getMarkerAttributes() {
        Map attrs = getInitialAttributes();
        attrs.put(IMarker.MESSAGE, descriptionText.getText());
        return attrs;
    }

    /**
     * Updates widget enablement for the dialog. Should be overridden by subclasses.
     */
    protected void updateEnablement() {
        descriptionText.setEditable(isEditable());
    }

    /**
     * @return <ul>
     * <li><code>true</code> if the marker is editable or the dialog is creating a new marker.</li> <li><code>false
     * </code> if the marker is not editable.</li>
     * </ul>
     */
    protected boolean isEditable() {
        if (marker == null) {
            return true;
        }
        return Util.isEditable(marker);
    }

    /**
     * Sets the marker type when creating a new marker.
     * 
     * @param type the marker type
     * 
     * @since 3.3 this method is protected.
     */
    protected void setType(String type) {
        this.type = type;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.eclipse.jface.window.Dialog#getDialogBoundsSettings()
     * 
     * @since 3.2
     */
    protected IDialogSettings getDialogBoundsSettings() {
        IDialogSettings settings = IDEWorkbenchPlugin.getDefault().getDialogSettings();
        IDialogSettings section = settings.getSection(DIALOG_SETTINGS_SECTION);
        if (section == null) {
            section = settings.addNewSection(DIALOG_SETTINGS_SECTION);
        }
        return section;
    }

    /**
     * Return the string that describes a modify marker operation. Subclasses may override to more specifically describe
     * the marker.
     * 
     * @since 3.3
     */
    protected String getModifyOperationTitle() {
        if (markerName == null) {
            // we don't know what kind of marker is being modified
            return MarkerMessages.DialogMarkerProperties_ModifyMarker;
        }
        return NLS.bind(MarkerMessages.qualifiedMarkerCommand_title, MarkerMessages.DialogMarkerProperties_Modify, markerName);//$NON-NLS-2$
    }

    /**
     * Return the string that describes a create marker operation. Subclasses may override to more specifically describe
     * the marker.
     * 
     * @since 3.3
     */
    protected String getCreateOperationTitle() {
        if (markerName == null) {
            // we don't know what kind of marker is being created
            return MarkerMessages.DialogMarkerProperties_CreateMarker;//$NON-NLS-1$
        }
        return NLS.bind(MarkerMessages.qualifiedMarkerCommand_title, MarkerMessages.DialogMarkerProperties_Create, markerName);//$NON-NLS-2$

    }

    /*
     * (non-Javadoc)
     * 
     * @see org.eclipse.jface.dialogs.Dialog#isResizable()
     */
    protected boolean isResizable() {
        return true;
    }
}
